#include "audio.h"

// Для записи звука используются средства Qt
// Для воспроизведения используется BASS

Audio::Audio()
{
    #ifdef Q_OS_WIN32
        os_windows = true;
    #endif

    #ifdef Q_OS_LINUX
         os_windows = false;
    #endif

    if( !init() )   // Инициализация BASS.
    {
        qDebug() << "Audio::Audio(): Не удалось инициализировать BASS";
        QMessageBox::warning(new QWidget(), "Ошибка BASS", "Неудалось инициализировать BASS");
        exit(1);
    }

    qDebug() << "Audio::Audio(): BASS инициализирован";

    b_isPlaying = false;    // Переменная сообщает о том, что идет воспроизведение

    connect(this, SIGNAL(playEnded()), SLOT(on_playEnded()));   // При окончании воспроизведения (не в случае нажатия "стоп")

    audioRecorder = NULL;

    len_mins = tr("%1").arg( (int) length / 60);
    len_secs = tr("%1").arg( length - ( len_mins.toInt() * 60));

    if( len_mins.count() == 1)
        len_mins.push_front("0");
    if( len_secs.count() == 1)
        len_secs.push_front("0");

    encoderSettings = new QAudioEncoderSettings;
    encoderSettings->setCodec("audio/pcm");
    encoderSettings->setQuality(QMultimedia::HighQuality);
}

QString Audio::getLenAsStr()
{
    // Получение длительности воспроизводимого файла как строки;
    return tr("%1:%2").arg(len_mins).arg(len_secs);
}

QString Audio::getCurAsStr()
{
    // Получение текущей позиции воспроизведения как строки
    return tr("%1:%2").arg(cur_mins).arg(cur_secs);
}

bool Audio::init()
{
    qDebug() << "Audio::init(): Вызвана функция инициализации BASS";

    if( HIWORD(BASS_GetVersion()) != BASSVERSION)
    {
        QMessageBox::warning(new QWidget(), "Ошибка BASS",
                             QString("Несовместимая версия BASS"));
        return false;
    }

    if( !BASS_Init(-1, 22050, NULL , 0, NULL) )
          return false;

    return true;
}

bool Audio::openFile( QString filename, bool repeat)
{
    qDebug() << "Audio::openFile(): Открываю файл " << filename;

    if( ! QFile::exists(filename))
    {
        qDebug() << tr("Audio::openFile(): Файла %1 не существует").arg(filename);

        QMessageBox::warning(new QWidget, "Ошибка", tr("Невозможно открыть файл\nФайла %1 несуществует").arg(filename));
        return false;
    }

    if( !BASS_ChannelStop(channel))
        qDebug() << "Audio::openFile: Не удалось остановить канал!";

    if( !filename.isEmpty())
    {
        if( !BASS_SampleFree(sample))    // Освобождение семпла
            qDebug() << "Audio::openFile: Не удалось освободить сэмпл";

        if( repeat )
        {
            if( os_windows)
                sample  = BASS_SampleLoad(false, filename.toStdWString().c_str(), 0, 0, 2, BASS_SAMPLE_LOOP );
            else
                sample  = BASS_SampleLoad(false, filename.toStdString().c_str(), 0, 0, 2, BASS_SAMPLE_LOOP );
        }
        else
        {
            if( os_windows)
                sample  = BASS_SampleLoad(false, filename.toStdWString().c_str(), 0, 0, 2, NULL );
            else
                sample  = BASS_SampleLoad(false, filename.toStdString().c_str(), 0, 0, 2, NULL );
        }
        if( !sample)
        {
            QMessageBox::critical(new QWidget, "Ошибка BASS", tr("Не удалось загрузить сэмпл файла %1").arg(filename));
            return false;
        }

        channel = BASS_SampleGetChannel(sample, false);

        if( !channel )
        {
            QMessageBox::critical(new QWidget, "Ошибка BASS", tr("Не удалось создать канал при открытии файла %1").arg(filename));
            return false;
        }

        QWORD lenInBytes = BASS_ChannelGetLength(channel, BASS_POS_BYTE);
        length = (int) BASS_ChannelBytes2Seconds(channel, lenInBytes);

        // Обнуление счетчика и вычисление длины
        curPos = 0;

        cur_mins = "00";
        cur_secs = "00";

        len_mins = tr("%1").arg( (int) length / 60);
        len_secs = tr("%1").arg( length - ( len_mins.toInt() * 60));

        if( len_mins.count() == 1)
            len_mins.push_front("0");
        if( len_secs.count() == 1)
            len_secs.push_front("0");

        openedFile = filename;
    }
    else
    {
        qDebug() << "Audio::openFile(): Ошибка: дана пустая строка";
        return false;
    }

    b_isOpened = true;
    return true;
}

void Audio::closeFile()
{
    BASS_ChannelStop(channel);
    BASS_SampleFree(sample);

    b_isOpened = false;
}

int Audio::reqCurPos()
{
    // Получение текущей позиции в секундах

    QWORD* posInBytes = new QWORD;                                      // Получение текущей позиции в байтах
    *posInBytes = BASS_ChannelGetPosition( channel, BASS_POS_BYTE);

    curPos = (int) BASS_ChannelBytes2Seconds(channel, *posInBytes);     // Преобразование в секунды

    if( ( !BASS_ChannelIsActive( channel )))
    {
        qDebug() << "Audio::reqCurPos(): Канал более не активен, это значит что воспроизведение окончено";
        emit playEnded();

        // Установка позиции в ноль
        curPos = 0;
        QWORD* nullPosition = new QWORD;
        *nullPosition = 0;

        BASS_ChannelSetPosition(channel, *nullPosition, NULL);

        delete nullPosition;
    }
    delete posInBytes;  // Учитывая сколько раз вызовется эта функция, не мудрено понять насколько засрется память,
                        // потому она высвобождается при каждом выходе.


    cur_mins = tr("%1").arg( (int) curPos / 60);
    cur_secs = tr("%1").arg( curPos - (cur_mins.toInt() * 60));

    if( cur_mins.count() == 1)
        cur_mins.push_front("0");
    if( cur_secs.count() == 1)
        cur_secs.push_front("0");

    return curPos;
}


void Audio::startPlay( bool repeat )
{
    if( !BASS_ChannelPlay( channel, repeat))
    {
        QMessageBox::warning(new QWidget(), "Ошибка BASS",
                                 QString("Не удалось воспроизвести файл " + openedFile));
    }
    else
    {
        qDebug() << "Audio::startPlay(): Начато воспроизведение";

        b_isPlaying = true;
        emit playStarted();
    }
}

void Audio::pausePlay()
{
    if( BASS_ChannelPause(channel))
    {
        qDebug() << "Audio::pausePlay(): Воспроизвдение приостановлено";

        b_isPlaying = false;

        emit playPaused();
    }
}

void Audio::stopPlay()
{
    qDebug() << "Audio::stopPlay(): Воспроизвдение остановлено";

    emit playStoped();
    b_isPlaying = false;
}

void Audio::on_playEnded()
{
    qDebug() << "Audio::on_playEnded(): Воспроизведение окончено";

    b_isPlaying = false;
}

bool Audio::startRecord(QString outputFile)
{
    if( outputFile.isEmpty() )
    {
        QMessageBox::warning(new QWidget, "Ошибка", "Нельзя использовать пустое имя");
        return false;
    }
    else
    {
        qDebug() << "Audio::startRecord(): Начинаю запись";

        if( audioRecorder == NULL )    // Если запись не настроена
        {
            audioRecorder = new QAudioRecorder;
            audioRecorder->setAudioInput( audioRecorder->defaultAudioInput() );
            audioRecorder->setEncodingSettings( *encoderSettings);
        }

        rec_currentFile = outputFile;

        // Запись в файл
        if( outputFile.right(3).toLower() != "wav")
            outputFile += ".wav";

        file = new QFile(outputFile);
        file->open(QFile::ReadWrite);           // Открытие существующего или создание нового файла

        audioRecorder->setOutputLocation( QUrl::fromLocalFile(outputFile));

        if( audioRecorder->isAvailable())
            audioRecorder->record();
        else
        {
            QString *error = new QString;

            if(audioRecorder->audioInputs().count() == 0)
                QMessageBox::warning(new QWidget, "Ошибка записи", "Нет доступных устройств записи");
            else
            {
                if( audioRecorder->availability() == 0)
                    *error = "AudioRecorder доступен";
                if( audioRecorder->availability() == 1)
                    *error = "Не найдены устройства записи";
                if( audioRecorder->availability() == 2)
                    *error = "Busy: The service must wait for access to necessary resources.";
                if( audioRecorder->availability() == 3)
                    *error = "Resource Error: The service could not allocate resources required to function correctly.";

                QMessageBox::warning(new QWidget, "Ошибка записи",
                                     "QAudioRecorder недоступен\nОшибка: " + *error);

                delete error;
            }

            return false;
        }

        if( (bool) audioRecorder->error() )    // Если произошла ошибка
        {
            QMessageBox::warning(new QWidget, "Ошибка записи", "Произошла ошибка при инициализации QAudioRecorder\nОшибка: " + audioRecorder->errorString());
            return false;
        }

        return true;
    }
}

void Audio::stopRecord()
{
    qDebug() << "Audio::stopRecord(): Заканчиваю запись";
    audioRecorder->stop();                                       // Остановка записи

    delete audioRecorder;

    file->close();
    delete file;

    audioRecorder = 0;      // Освобождение памяти и создание нулевого указателя, для того чтобы проверка на готовность QAudioRecorder не проходила успешно
                            // и процесс настройки запускался заново для нового файла

    QString *format = new QString( rec_currentFile.right(3) );  // Получение типа файла

    QProcess *p = new QProcess();   // Для работы с процессами
    QDir     *d = new QDir;         // Для работы с файлами

    if( format->toLower() != "wav")    // По умолчанию QAudioRecorder записывает только wav, потому надо преобразовывать через lame
    {
        qDebug() << "Audio::stopRecord(): Формат конечного файла должен быть" << format->toLower() << ",а не wav. Начинаю преобразование из wav в" << *format;

        QStringList args;
        args << "-V2" << rec_currentFile + ".wav" << rec_currentFile;   // Второй: записанный в wav файл (имяФайла.тип.wav)

        qDebug() << "Audio::stopRecord(): Запускаю lame.exe";

        p->start("lame.exe", args);         // Запуск lame для перекодирования и получения из "имяФайла.тип.wav" "имяФайла.тип"

        qDebug() << "Audio::stopRecord(): lame запущен. Ожидание завершения";

        if( p->waitForFinished() )           // Если процесс завершился успехом
        {
            QString lameOutput = p->readAllStandardError() + p->readAllStandardOutput();
            qDebug() << lameOutput;

            if( p->exitCode() )   // Если lame завершился с ошибкой
                QMessageBox::warning(new QWidget, "Ошибка lame", "Программа lame завершилась с ошибкой:\n" + lameOutput);
            else
            {
                qDebug() << "Audio::stopRecord(): Все необходимые действия при остановке записи выполнены. Файл " << rec_currentFile << " теперь доступен";

                d->remove(rec_currentFile + ".wav");
            }
        }
        else
        {// При запуске lame произошла ошибка
            QMessageBox::warning(new QWidget, "Ошибка записи", "QProcess сообщил об ошибке lame:\n" + p->errorString() + "\nЗапись не удалась");

            d->remove(rec_currentFile + ".wav");
        }
    }

    delete format;
    delete p;
    delete d;

    rec_currentFile = "";   // Очистка строки содержащей путь к текущему записываемому файлу, поскольку файл теперь не записывается
}

QString Audio::getLengthOfFile(QString fileName)
{
    QFile *check = new QFile(fileName);

    if( check->exists() )
    {
        if( fileName.right(3).toLower() == "wav" ||
            fileName.right(3).toLower() == "mp3" ||
            fileName.right(3).toLower() == "ogg")
            {

            QString *lenAsStr = new QString;   // Длина файла в виде строки
            int     *len      = new int;       // Длина файла в секундах

            HSAMPLE  *tempSample  = new HSAMPLE;
            HCHANNEL *tempChannel = new HCHANNEL;

            // Получение длины в секундах
            if( os_windows )
                *tempSample  = BASS_SampleLoad(false, fileName.toStdWString().c_str(), 0, 0, 2, NULL );
            else
                *tempSample  = BASS_SampleLoad(false, fileName.toStdString().c_str(), 0, 0, 2, NULL );

            *tempChannel = BASS_SampleGetChannel(*tempSample, false);

            if( !tempSample)
            {
                QMessageBox::critical(new QWidget, "Ошибка BASS", tr("Ошибка получения длительности\n" \
                                                                     "Не удалось загрузить сэмпл файла %1").arg(fileName));
                return "error";
            }

            if( !tempChannel )
            {
                QMessageBox::critical(new QWidget, "Ошибка BASS", tr("Ошибка получения длительности\n" \
                                                                     "Не удалось создать канал из файла %1").arg(fileName));
                return "error";
            }

            QWORD* lenInBytes = new QWORD;
            *lenInBytes = BASS_ChannelGetLength(*tempChannel, BASS_POS_BYTE);

            *len =(int) BASS_ChannelBytes2Seconds(*tempChannel, *lenInBytes);

            // Получение длины как строки
            QString *mins = new QString(tr("%1").arg( (int) *len / 60));               // Получение минут
            QString *secs = new QString(tr("%1").arg( *len - ( mins->toInt() * 60)));  // Получение секунд

            if( mins->count() == 1)      // Если число однозначное, добавляем ноль
                mins->push_front("0");
            if( secs->count() == 1)
                secs->push_front("0");

            *lenAsStr = *mins + ":" + *secs;

            // Освобождение памяти
            delete len;
            delete tempSample;
            delete tempChannel;
            delete lenInBytes;
            delete mins;
            delete secs;
            delete check;

            return *lenAsStr;
        }
        else
        {
            QMessageBox::warning(new QWidget, "Ошибка", "Ошибка получения длины файла.\nФайл " + fileName + " не является аудиофайлом");

            delete check;
            return "<b><color=red>Ошибка";
        }
    }
    else
    {
        QMessageBox::warning(new QWidget, "Ошибка", "Ошибка получения длины файла.\nФайла " + fileName + " несуществует");

        delete check;
        return "<b><color=red>Ошибка";
    }
}

QStringList Audio::getLengthsOfFiles(QStringList fileNames)
{
    int *i = new int;

    qDebug() << "Audio::getLengthsOfFiles(): Получение длин файлов: " ;
    for( *i = 0;*i < fileNames.count(); *i++)
            qDebug() << tr("%1: %2").arg(*i+1).arg(fileNames.at(*i));

    qDebug() << "Audio::getLengthsOfFiles(): Начинаю вычисление...";

    // Подготовка переменных перед циклом
    QFile* check;
    QStringList result;

    int      *len;       // Длина файла в секундах

    HSAMPLE  *tempSample;
    HCHANNEL *tempChannel;

    QWORD* lenInBytes;

    QString *mins;
    QString *secs;

    for( *i = 0; *i < fileNames.count(); *i++) // Вычесляет длину для каждого файла в списке
    {
        check = new QFile(fileNames.at(*i));

        if( check->exists()) // Проверка существования файла
        {
            // Проверка на соответствие
            if( fileNames.at(*i).right(3).toLower() == "wav" ||
                fileNames.at(*i).right(3).toLower() == "mp3" ||
                fileNames.at(*i).right(3).toLower() == "ogg")
            {
                // Инициализация переменных
                len         = new int;
                tempSample  = new HSAMPLE;
                tempChannel = new HCHANNEL;
                lenInBytes  = new QWORD;
                mins        = new QString;
                secs        = new QString;

                // Получение данных
                *tempSample  = BASS_SampleLoad(false, fileNames.at(*i).toStdWString().c_str(), 0, 0, 2, NULL);
                *tempChannel = BASS_SampleGetChannel(*tempSample, false);

                *lenInBytes  = BASS_ChannelGetLength(*tempChannel, BASS_POS_BYTE);
                *len         = (int) BASS_ChannelBytes2Seconds(*tempChannel, *lenInBytes);

                *mins        = tr("%1").arg( (int) *len / 60);
                *secs        = tr("%1").arg( *len - ( mins->toInt() * 60));

                // Получение длины звука как строки
                if( mins->count() == 1)
                    mins->push_front("0");
                if( secs->count() == 1)
                    secs->push_front("0");  // Если число однозначное, добавляем ноль

                result.push_back( *mins + ":" + *secs);

                qDebug() << tr("Audio::getLengthsOfFiles(): %1: %2\t%3:%4").arg(*i+1).arg(fileNames.at(*i)).arg(*mins).arg(*secs);

                // Высвобождение памяти
                delete len;
                delete tempSample;
                delete tempChannel;
                delete lenInBytes;
                delete mins;
                delete secs;
            }
            else
            {
                qDebug() << tr("Audio::getLengthsOfFiles(): %1: %2\tНе получено. Тип файла не wav/ogg/mp3").arg(*i+1).arg(fileNames.at(*i));

                QMessageBox::warning(new QWidget, "Ошибка", "Ошибка получения длины файла.\nФайл " + fileNames.at(*i) + " не является аудиофайлом");

                result.push_back("<b><color=red>Ошибка");
           }
        }
        else
        {

            QMessageBox::warning(new QWidget, "Ошибка", "Ошибка получения длины файла.\nФайла " + fileNames.at(*i) + " несуществует");

            result.push_back("<b><color=red>Ошибка");
        }

        delete check;
    }

    delete i;
    return result;
}

QString Audio::sec2str(int seconds)
{
    qDebug() << tr("Audio::sec2str(): Конвертирование %1 секунд(ы) в строку").arg(seconds);

    QString mins = tr("%1").arg( (int) seconds / 60);
    QString secs = tr("%1").arg( seconds - ( mins.toInt() * 60));

    if( mins.count() == 1)
        mins.push_front("0");
    if( secs.count() == 1)
        secs.push_front("0");

    qDebug() << tr("Audio::sec2str(): Результат: %1 секунд(ы) - %2:%3").arg(seconds).arg(mins).arg(secs);

    return tr("%1:%2").arg(mins).arg(secs);
}

bool Audio::convertFile(QString origFile, QString destFile)
{
    qDebug() << "Audio::convertFile: Вызвана функция для конвертации файла";
    qDebug() << "Audio::convertFile: Оригинальный файл:\t" << origFile;
    qDebug() << "Audio::convertFile: Конечный файл:\t"    << destFile;

    QProcess *lame = new QProcess;
    QStringList *args = new QStringList;

    *args << "-V2" << origFile << destFile;

    lame->setProgram("lame.exe");
    lame->setArguments(*args);

    qDebug() << "Audio::convertFile: Запуск lame";
    lame->start();

    if( lame->waitForStarted(-1))
    {
        qDebug() << "Audio::convertFile: lame успешно запущен. Ожидание завершения";

        lame->waitForFinished();           // Если процесс завершился успехом

        qDebug() << "Audio::convertFile: lame завершил работу с выводом:";
        qDebug() << "Audio::convertFile: " << lame->readAll();

        if( lame->exitCode() )   // Если lame завершился с ошибкой
        {
            qDebug() << "Audio::convertFile: lame завершил работу с ошибкой";
            QMessageBox::warning(new QWidget, "Ошибка lame", "Программа lame завершилась с ошибкой");
            return false;
        }
    }
    else
    {
        qDebug() << "Audio::convertFile: Не удалось запустить lame. Путь к lame данный QProcess - " << lame->program();
        QMessageBox::warning(new QWidget(), "Ошибка", "Не удалось запустить lame для конвертации файла в нужный формат\nУбедитесь что lame.exe находится в папке с программой\nБыла попытка запустить lame из " + lame->program());
        return false;
    }

    qDebug() << "Audio::convertFile: Конвертация успешно завершена";

    return true;
}
